package com.zk.config.api.service;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.apache.zookeeper.ZooKeeper;
import com.zk.config.api.util.ZkCom;
import com.zk.config.api.util.ZkOperate;
import lombok.Getter;
import lombok.Setter;

public class ZkCache {
	private static Logger logger = Logger.getLogger(ZkCache.class);
	@Setter
	private ZooKeeper zk;
	private String zkRoot = "/";
	private Map<String, String> fileValue = new HashMap<>();
	@Setter@Getter
	private Properties properties;
	@Setter@Getter
	private String log4jPathName = "";
	private boolean watch = false;
	@Setter@Getter
	private Map<String, Properties> fileProperties = new HashMap<>();
	
	public ZkCache(ZooKeeper zk, boolean watch) {
		this.zk = zk;
		this.watch = watch;
	}
	
	public void init() {
		read(zkRoot, watch);
	}
	
	public void refresh(){
		Map<String, String> fileValue = new HashMap<>();
		Map<String, Properties> fileProperties = new HashMap<>();
		Properties properties = new Properties();
		read(fileValue, fileProperties, properties, zkRoot, false);
		if (properties.size() > 0 && this.properties != null) {
			Properties properties2 = this.properties;
			this.properties = properties;
			properties2.clear();
			properties2 = null;
		} else {
			properties.clear();
			properties = null;
		}
		if (fileProperties.size() > 0) {
			Map<String, Properties> fileProperties2 = this.fileProperties;
			this.fileProperties = fileProperties;
			fileProperties2.clear();
			fileProperties2 = null;
		} else {
			fileProperties.clear();
			fileProperties = null;
		}
		if (fileValue.size() > 0) {
			Map<String, String> fileValue2 = this.fileValue;
			this.fileValue = fileValue;
			fileValue2.clear();
			fileValue2 = null;
		} else {
			fileValue.clear();
			fileValue = null;
		}
	}
	
	public void read(Map<String, String> fileValue, Map<String, Properties> fileProperties, Properties properties, String path, boolean watch){
		String zkPath = getZkPath(path);
		String data = null;
		if (ZkOperate.existsNode(zk, zkPath, watch) != null) {
			data = ZkOperate.getNodeData(zk, zkPath);
			if (data != null && data.length() > 0) {
				if (zkPath.endsWith(".properties")) {
					Properties p = new Properties();
					try {
						p.load(new ByteArrayInputStream(data.getBytes()));
					} catch (IOException e) {
						e.printStackTrace();
						logger.error("It isnt a valid properties file format.");
						return;
					}
					if(zkPath.endsWith(log4jPathName)) {
						PropertyConfigurator.configure(p);
					} else {
						if(fileProperties != null) {
							fileProperties.put(zkPath, p);
						}
					}
					
					if (properties != null) {
						properties.putAll(p);
					}
				} else {
					if(fileValue != null) {
						fileValue.put(zkPath, data);
					}
				}
			}
			
			List<String> childrens = ZkOperate.getChildren(zk, zkPath, watch);
			if (childrens != null && childrens.size() > 0) {
				zkPath = "/".equals(zkPath)?"/":(zkPath+"/");
				for(String c:childrens) {
					read(fileValue, fileProperties, properties, zkPath+c, watch);
				}
			}
		}
	}
	
	public void read(String path, boolean watch) {
		read(fileValue, fileProperties, properties, path, watch);
	}
	
	public void reloadIfNodeDelete(String path){
		String zkPath = getZkPath(path);
		Map<String, String> fileValue = new HashMap<>();
		Map<String, Properties> fileProperties = new HashMap<>();
		fileValue.putAll(this.fileValue);
		String newZkPath = ZkCom.getZkEndPath(zkPath);
		for(String key:this.fileValue.keySet()) {
			if(ZkCom.getZkEndPath(key).startsWith(newZkPath)) {
				fileValue.remove(key);
			}
		}
		if(fileValue.containsKey(zkPath)) {
			fileValue.remove(zkPath);
		}
		fileProperties.putAll(this.fileProperties);
		for(String key:this.fileProperties.keySet()) {
			if(ZkCom.getZkEndPath(key).startsWith(newZkPath)) {
				removeProperties(fileProperties, key);
				fileProperties.remove(key);
			}
		}
		if(fileProperties.containsKey(zkPath)) {
			removeProperties(fileProperties, zkPath);
			fileProperties.remove(zkPath);
		}
		
		Map<String, String> fileValue2 = this.fileValue;
		Map<String, Properties> fileProperties2 = this.fileProperties;
		this.fileValue = fileValue;
		this.fileProperties = fileProperties;
		fileValue2.clear();
		fileValue2 = null;
		fileProperties2.clear();
		fileProperties2 = null;
	}
	
	private void removeProperties(Map<String, Properties> fileProperties, String key){
		if(this.properties == null) return;
		Properties properties = fileProperties.get(key);
		if (properties != null && properties.size() > 0) {
			for(Object k:properties.keySet()) {
				Object val = properties.get(k);
				Object val2 = this.properties.get(k);
				if (val != null && val.equals(val2)) {
					this.properties.remove(k);
				}
			}
		}
	}
	
	public void reloadIfNodeCreateOrChange(String path){
		String zkPath = getZkPath(path);
		String data = ZkOperate.getNodeData(zk, zkPath);
		
		if (data != null && data.length() > 0) {
			if (zkPath.endsWith(".properties")) {
				Properties p = new Properties();
				try {
					p.load(new ByteArrayInputStream(data.getBytes()));
				} catch (IOException e) {
					e.printStackTrace();
					logger.error("It isnt a valid properties file format.");
					return;
				}
				if(zkPath.endsWith(log4jPathName)) {
					PropertyConfigurator.configure(p);
				} else {
					if(fileProperties != null) {
						fileProperties.put(zkPath, p);
					}
				}
				if(properties != null) {
					properties.putAll(p);
				}
			} else {
				if(fileValue != null) {
					fileValue.put(zkPath, data);
				}
			}
		}
	}
	
	public String getPropertiesValue(String key){
		if (fileProperties.size() > 0) {
			for(String pk:fileProperties.keySet()) {
				Properties pro = fileProperties.get(pk);
				if(pro.containsKey(key)) {
					return pro.getProperty(key);
				}
			}
		}
		return null;
	}
	
	public Properties getPropertiesByNodePath(String nodePath){
		if (fileProperties.size() > 0) {
			return fileProperties.get(nodePath);
		}
		return null;
	}
	
	public String getPropertiesValue(String fileName, String key){
		String filePath = getZkPath(fileName);
		if (fileProperties.containsKey(filePath)) {
			Properties pro = fileProperties.get(filePath);
			if(pro.containsKey(key)) {
				return pro.getProperty(key);
			}
		}
		return null;
	}
	
	public String getFileValue(String fileName){
		String filePath = getZkPath(fileName);
		if (fileValue.containsKey(filePath)) {
			return fileValue.get(filePath);
		}
		return null;
	}
	
	/**
	 * 
	 * @param ...path
	 * @param value
	 */
	public boolean addProperties(String ...pathValue) {
		boolean result = false;
		if (pathValue != null && pathValue.length > 1) {
			String content = pathValue[pathValue.length - 1];
			String key[] = new String[pathValue.length - 1];
			for(int i = 0; i < key.length; i++) {
				key[i] = pathValue[i];
			}
			String path = getZkPath(key);
			if(ZkCom.isNotEmpty(path) && ZkCom.isNotEmpty(content)) {
				Properties p = new Properties();
				byte[] b = content.getBytes();
				try {
					p.load(new ByteArrayInputStream(b));
				} catch (IOException e) {
					e.printStackTrace();
					logger.error("It isnt a valid properties file format.");
					return result;
				}
				if(ZkOperate.createNode(zk, path, b)) {
					fileProperties.put(path, p);
					result = true;
				}
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @param ...path
	 * @param value
	 */
	public boolean updateProperties(String ...pathValue) {
		boolean result = false;
		if (pathValue != null && pathValue.length > 1) {
			String content = pathValue[pathValue.length - 1];
			String key[] = new String[pathValue.length - 1];
			for(int i = 0; i < key.length; i++) {
				key[i] = pathValue[i];
			}
			String path = getZkPath(key);
			if(ZkCom.isNotEmpty(path) && ZkCom.isNotEmpty(content)) {
				Properties p = new Properties();
				byte[] b = content.getBytes();
				try {
					p.load(new ByteArrayInputStream(b));
				} catch (IOException e) {
					e.printStackTrace();
					logger.error("It isnt a valid properties file format.");
					return result;
				}
				if(ZkOperate.setNodeData(zk, path, b)) {
					fileProperties.put(path, p);
					result = true;
				}
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @param ...path
	 * @param value
	 */
	public boolean deleteProperties(String ...path) {
		boolean result = false;
		if (path != null && path.length > 0) {
			String keyPath = getZkPath(path);
			if(ZkCom.isNotEmpty(keyPath)) {
				if(ZkOperate.deleteNode(zk, keyPath)) {
					fileProperties.remove(keyPath);
					result = true;
				}
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @param ...path
	 * @param value
	 */
	public boolean addFileValue(String ...pathValue) {
		boolean result = false;
		if (pathValue != null && pathValue.length > 1) {
			String content = pathValue[pathValue.length - 1];
			String key[] = new String[pathValue.length - 1];
			for(int i = 0; i < key.length; i++) {
				key[i] = pathValue[i];
			}
			String path = getZkPath(key);
			if(ZkCom.isNotEmpty(path) && ZkCom.isNotEmpty(content)) {
				if(ZkOperate.createNode(zk, path, content)) {
					fileValue.put(path, content);
					result = true;
				}
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @param ...path
	 * @param value
	 */
	public boolean updateFileValue(String ...pathValue) {
		boolean result = false;
		if (pathValue != null && pathValue.length > 1) {
			String content = pathValue[pathValue.length - 1];
			String key[] = new String[pathValue.length - 1];
			for(int i = 0; i < key.length; i++) {
				key[i] = pathValue[i];
			}
			String path = getZkPath(key);
			if(ZkCom.isNotEmpty(path) && ZkCom.isNotEmpty(content)) {
				if(ZkOperate.setNodeData(zk, path, content)) {
					fileValue.put(path, content);
					result = true;
				}
			}
		}
		return result;
	}
	
	/**
	 * 
	 * @param ...path
	 * @param value
	 */
	public boolean deleteFileValue(String ...path) {
		boolean result = false;
		if (path != null && path.length > 0) {
			String keyPath = getZkPath(path);
			if(ZkCom.isNotEmpty(keyPath)) {
				if(ZkOperate.deleteNode(zk, keyPath)) {
					fileValue.remove(keyPath);
					result = true;
				}
			}
		}
		return result;
	}
	
	public String getZkPath(String ...path){
		String pathStr = "";
		if (path != null && path.length > 0) {
			for(String p:path) {
				pathStr += ZkCom.getZkPath(p);
			}
		}
		return pathStr;
	}
}
